/*-------------------------------------------------------------------------
 *
 * wparser_def.c
 *		默认文本搜索解析器
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/wparser_def.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <limits.h>

#include "catalog/pg_collation.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "tsearch/ts_locale.h"
#include "tsearch/ts_public.h"
#include "tsearch/ts_type.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"


/* 定义我以启用解析器行为的跟踪 */
/* #define WPARSER_TRACE */


/* 输出令牌类别 */

#define ASCIIWORD		1
#define WORD_T			2
#define NUMWORD			3
#define EMAIL			4
#define URL_T			5
#define HOST			6
#define SCIENTIFIC		7
#define VERSIONNUMBER	8
#define NUMPARTHWORD	9
#define PARTHWORD		10
#define ASCIIPARTHWORD	11
#define SPACE			12
#define TAG_T			13
#define PROTOCOL		14
#define NUMHWORD		15
#define ASCIIHWORD		16
#define HWORD			17
#define URLPATH			18
#define FILEPATH		19
#define DECIMAL_T		20
#define SIGNEDINT		21
#define UNSIGNEDINT		22
#define XMLENTITY		23

#define LASTNUM			23

static const char *const tok_alias[] = {
	"",
	"asciiword",
	"word",
	"numword",
	"email",
	"url",
	"host",
	"sfloat",
	"version",
	"hword_numpart",
	"hword_part",
	"hword_asciipart",
	"blank",
	"tag",
	"protocol",
	"numhword",
	"asciihword",
	"hword",
	"url_path",
	"file",
	"float",
	"int",
	"uint",
	"entity"
};

static const char *const lex_descr[] = {
	"",
	"Word, all ASCII",
	"Word, all letters",
	"Word, letters and digits",
	"Email address",
	"URL",
	"Host",
	"Scientific notation",
	"Version number",
	"Hyphenated word part, letters and digits",
	"Hyphenated word part, all letters",
	"Hyphenated word part, all ASCII",
	"Space symbols",
	"XML tag",
	"Protocol head",
	"Hyphenated word, letters and digits",
	"Hyphenated word, all ASCII",
	"Hyphenated word, all letters",
	"URL path",
	"File or path name",
	"Decimal notation",
	"Signed integer",
	"Unsigned integer",
	"XML entity"
};


/* 解析器状态 */

typedef enum
{
	TPS_Base = 0,
	TPS_InNumWord,
	TPS_InAsciiWord,
	TPS_InWord,
	TPS_InUnsignedInt,
	TPS_InSignedIntFirst,
	TPS_InSignedInt,
	TPS_InSpace,
	TPS_InUDecimalFirst,
	TPS_InUDecimal,
	TPS_InDecimalFirst,
	TPS_InDecimal,
	TPS_InVerVersion,
	TPS_InSVerVersion,
	TPS_InVersionFirst,
	TPS_InVersion,
	TPS_InMantissaFirst,
	TPS_InMantissaSign,
	TPS_InMantissa,
	TPS_InXMLEntityFirst,
	TPS_InXMLEntity,
	TPS_InXMLEntityNumFirst,
	TPS_InXMLEntityNum,
	TPS_InXMLEntityHexNumFirst,
	TPS_InXMLEntityHexNum,
	TPS_InXMLEntityEnd,
	TPS_InTagFirst,
	TPS_InXMLBegin,
	TPS_InTagCloseFirst,
	TPS_InTagName,
	TPS_InTagBeginEnd,
	TPS_InTag,
	TPS_InTagEscapeK,
	TPS_InTagEscapeKK,
	TPS_InTagBackSleshed,
	TPS_InTagEnd,
	TPS_InCommentFirst,
	TPS_InCommentLast,
	TPS_InComment,
	TPS_InCloseCommentFirst,
	TPS_InCloseCommentLast,
	TPS_InCommentEnd,
	TPS_InHostFirstDomain,
	TPS_InHostDomainSecond,
	TPS_InHostDomain,
	TPS_InPortFirst,
	TPS_InPort,
	TPS_InHostFirstAN,
	TPS_InHost,
	TPS_InEmail,
	TPS_InFileFirst,
	TPS_InFileTwiddle,
	TPS_InPathFirst,
	TPS_InPathFirstFirst,
	TPS_InPathSecond,
	TPS_InFile,
	TPS_InFileNext,
	TPS_InURLPathFirst,
	TPS_InURLPathStart,
	TPS_InURLPath,
	TPS_InFURL,
	TPS_InProtocolFirst,
	TPS_InProtocolSecond,
	TPS_InProtocolEnd,
	TPS_InHyphenAsciiWordFirst,
	TPS_InHyphenAsciiWord,
	TPS_InHyphenWordFirst,
	TPS_InHyphenWord,
	TPS_InHyphenNumWordFirst,
	TPS_InHyphenNumWord,
	TPS_InHyphenDigitLookahead,
	TPS_InParseHyphen,
	TPS_InParseHyphenHyphen,
	TPS_InHyphenWordPart,
	TPS_InHyphenAsciiWordPart,
	TPS_InHyphenNumWordPart,
	TPS_InHyphenUnsignedInt,
	TPS_Null					/* 最后状态（虚假值） */
} TParserState;

/* 前向声明 */
struct TParser;

typedef int (*TParserCharTest) (struct TParser *);	/* 任何 p_is* 函数
													 * 除了 p_iseq */
typedef void (*TParserSpecial) (struct TParser *);	/* 特殊情况下的特殊处理程序... */

typedef struct
{
	TParserCharTest isclass;
	char		c;
	uint16		flags;
	TParserState tostate;
	int			type;
	TParserSpecial special;
} TParserStateActionItem;

/* TParserStateActionItem.flags 中的标志位 */
#define A_NEXT		0x0000
#define A_BINGO		0x0001
#define A_POP		0x0002
#define A_PUSH		0x0004
#define A_RERUN		0x0008
#define A_CLEAR		0x0010
#define A_MERGE		0x0020
#define A_CLRALL	0x0040

typedef struct TParserPosition
{
	int			posbyte;		/* 解析器的位置（以字节为单位） */
	int			poschar;		/* 解析器的位置（以字符为单位） */
	int			charlen;		/* 当前字符的长度 */
	int			lenbytetoken;	/* 到目前为止的令牌长度（以字节为单位） */
	int			lenchartoken;	/* 以及以字符为单位 */
	TParserState state;
	struct TParserPosition *prev;
	const TParserStateActionItem *pushedAtAction;
} TParserPosition;

typedef struct TParser
{
	/* 字符串和位置信息 */
	char	   *str;			/* 多字节字符串 */
	int			lenstr;			/* mbstring 的长度 */
	wchar_t    *wstr;			/* 宽字符字符串 */
	pg_wchar   *pgwstr;			/* C-locale 的宽字符字符串 */
	bool		usewide;

	/* 解析状态 */
	int			charmaxlen;
	TParserPosition *state;
	bool		ignore;
	bool		wanthost;

	/* 笨拙的字符 */
	char		c;

	/* 输出 */
	char	   *token;
	int			lenbytetoken;
	int			lenchartoken;
	int			type;
} TParser;


/* 这里的前向声明 */
static bool fc_TParserGet(TParser *fc_prs);


static TParserPosition *
fc_newTParserPosition(TParserPosition *fc_prev)
{
	TParserPosition *fc_res = (TParserPosition *) palloc(sizeof(TParserPosition));

	if (fc_prev)
		memcpy(fc_res, fc_prev, sizeof(TParserPosition));
	else
		memset(fc_res, 0, sizeof(TParserPosition));

	fc_res->prev = fc_prev;

	fc_res->pushedAtAction = NULL;

	return fc_res;
}

static TParser *
fc_TParserInit(char *fc_str, int fc_len)
{
	TParser    *fc_prs = (TParser *) palloc0(sizeof(TParser));

	fc_prs->charmaxlen = pg_database_encoding_max_length();
	fc_prs->str = fc_str;
	fc_prs->lenstr = fc_len;

	/*
	 * 仅在最大编码长度 > 1 时使用宽字符代码。
	 */
	if (fc_prs->charmaxlen > 1)
	{
		pg_locale_t fc_mylocale = 0;	/* TODO */

		fc_prs->usewide = true;
		if (database_ctype_is_c)
		{
			/*
			 * char2wchar 不适用于 C-locale，且 sizeof(pg_wchar) 可能
			 * 与 sizeof(wchar_t) 不同
			 */
			fc_prs->pgwstr = (pg_wchar *) palloc(sizeof(pg_wchar) * (fc_prs->lenstr + 1));
			pg_mb2wchar_with_len(fc_prs->str, fc_prs->pgwstr, fc_prs->lenstr);
		}
		else
		{
			fc_prs->wstr = (wchar_t *) palloc(sizeof(wchar_t) * (fc_prs->lenstr + 1));
			char2wchar(fc_prs->wstr, fc_prs->lenstr + 1, fc_prs->str, fc_prs->lenstr,
					   fc_mylocale);
		}
	}
	else
		fc_prs->usewide = false;

	fc_prs->state = fc_newTParserPosition(NULL);
	fc_prs->state->state = TPS_Base;

#ifdef WPARSER_TRACE
	fprintf(stderr, "parsing \"%.*s\"\n", fc_len, fc_str);
#endif

	return fc_prs;
}

/*
 * 作为完全 TParserInit 的替代方法，可以创建一个
 * TParserCopy，它基本上是一个没有字符串私有副本的常规 TParser -
 * 而是使用另一个 TParser 的字符串。
 * 这非常有用，因为在某些地方 TParser 是递归创建的，
 * 如果源字符串较长，反复复制字符串可能会导致重大低效。
 * 新解析器从原始解析器的当前位置开始解析。
 *
 * 显然，在复制之前不能关闭原始 TParser。
 */
static TParser *
fc_TParserCopyInit(const TParser *fc_orig)
{
	TParser    *fc_prs = (TParser *) palloc0(sizeof(TParser));

	fc_prs->charmaxlen = fc_orig->charmaxlen;
	fc_prs->str = fc_orig->str + fc_orig->state->posbyte;
	fc_prs->lenstr = fc_orig->lenstr - fc_orig->state->posbyte;
	fc_prs->usewide = fc_orig->usewide;

	if (fc_orig->pgwstr)
		fc_prs->pgwstr = fc_orig->pgwstr + fc_orig->state->poschar;
	if (fc_orig->wstr)
		fc_prs->wstr = fc_orig->wstr + fc_orig->state->poschar;

	fc_prs->state = fc_newTParserPosition(NULL);
	fc_prs->state->state = TPS_Base;

#ifdef WPARSER_TRACE
	fprintf(stderr, "parsing copy of \"%.*s\"\n", fc_prs->lenstr, fc_prs->str);
#endif

	return fc_prs;
}


static void fc_TParserClose(TParser *fc_prs)
{
	while (fc_prs->state)
	{
		TParserPosition *fc_ptr = fc_prs->state->prev;

		pfree(fc_prs->state);
		fc_prs->state = fc_ptr;
	}

	if (fc_prs->wstr)
		pfree(fc_prs->wstr);
	if (fc_prs->pgwstr)
		pfree(fc_prs->pgwstr);

#ifdef WPARSER_TRACE
	fprintf(stderr, "closing parser\n");
#endif
	pfree(fc_prs);
}

/*
 * 关闭使用 TParserCopyInit 创建的解析器
 */
static void fc_TParserCopyClose(TParser *fc_prs)
{
	while (fc_prs->state)
	{
		TParserPosition *fc_ptr = fc_prs->state->prev;

		pfree(fc_prs->state);
		fc_prs->state = fc_ptr;
	}

#ifdef WPARSER_TRACE
	fprintf(stderr, "closing parser copy\n");
#endif
	pfree(fc_prs);
}


/*
 * 字符类型支持函数，相当于 is* 宏，但
 * 可以处理任何可能的编码和区域设置。注意：
 *	- 在多字节编码和 C-locale 下，isw* 函数可能失败
 *	  或返回错误结果。
 *	- 多字节编码和 C-locale 通常用于
 *	  亚洲语言。
 *	- 如果区域设置是 C，则使用 pgwstr 而不是 wstr。
 */
#ifdef FDD //cppcheck，必须先断言 'prs'，并且编译定义不能在其他定义中
#define p_iswhat(type, nonascii)											\
																			\
static int																	\
p_is##type(TParser *fc_prs)													\
{																			\
	Assert(fc_prs && fc_prs->state);												\
	if (fc_prs->usewide)														\
	{																		\
		if (fc_prs->pgwstr)													\
		{																	\
			unsigned int fc_c = *(fc_prs->pgwstr + fc_prs->state->poschar);			\
			if (fc_c > 0x7f)													\
				return nonascii;											\
			return is##type(fc_c);												\
		}																	\
		return isw##type(*(fc_prs->wstr + fc_prs->state->poschar));				\
	}																		\
	return is##type(*(unsigned char *) (fc_prs->str + fc_prs->state->posbyte));	\
}																			\
																			\
static int																	\
p_isnot##type(TParser *fc_prs)													\
{																			\
	return !p_is##type(fc_prs);												\
}
#else
#define p_iswhat(type, nonascii)											\
																			\
static int																	\
p_is##type(TParser *fc_prs)													\
{																			\
	Assert(fc_prs->state);														\
	if (fc_prs->usewide)														\
	{																		\
		if (fc_prs->pgwstr)													\
		{																	\
			unsigned int fc_c = *(fc_prs->pgwstr + fc_prs->state->poschar);			\
			if (fc_c > 0x7f)													\
				return nonascii;											\
			return is##type(fc_c);												\
		}																	\
		return isw##type(*(fc_prs->wstr + fc_prs->state->poschar));				\
	}																		\
	return is##type(*(unsigned char *) (fc_prs->str + fc_prs->state->posbyte));	\
}																			\
																			\
static int																	\
p_isnot##type(TParser *fc_prs)													\
{																			\
	return !p_is##type(fc_prs);												\
}

#endif


/*
 * 在 C 区域设置和多字节编码下，任何非 ASCII 符号都被视为
 * 字母字符，但不是其他字符类的成员。
 */
p_iswhat(alnum, 1)
p_iswhat(alpha, 1)
p_iswhat(digit, 0)
p_iswhat(lower, 0)
p_iswhat(print, 0)
p_iswhat(punct, 0)
p_iswhat(space, 0)
p_iswhat(upper, 0)
p_iswhat(xdigit, 0)

/* p_iseq 应仅用于 ASCII 符号 */

static int fc_pc_p_iseq(TParser *fc_prs, char fc_c)
{
#ifdef FDD //cppcheck
	Assert(fc_prs && fc_prs->state);
#else
	Assert(fc_prs->state);
#endif
	return ((fc_prs->state->charlen == 1 && *(fc_prs->str + fc_prs->state->posbyte) == fc_c)) ? 1 : 0;
}

static int fc_p_isEOF(TParser *fc_prs)
{
#ifdef FDD //cppcheck
	Assert(fc_prs && fc_prs->state);
#else
	Assert(fc_prs->state);
#endif
	return (fc_prs->state->posbyte == fc_prs->lenstr || fc_prs->state->charlen == 0) ? 1 : 0;
}

static int fc_p_iseqC(TParser *fc_prs)
{
#ifdef FDD //cppcheck
	Assert(fc_prs);
#endif
	return fc_pc_p_iseq(fc_prs, fc_prs->c);
}

static int fc_p_isneC(TParser *fc_prs)
{
#ifdef FDD //cppcheck
	Assert(fc_prs);
#endif
	return !fc_pc_p_iseq(fc_prs, fc_prs->c);
}

static int fc_p_isascii(TParser *fc_prs)
{
	return (fc_prs->state->charlen == 1 && isascii((unsigned char) *(fc_prs->str + fc_prs->state->posbyte))) ? 1 : 0;
}

static int fc_p_isasclet(TParser *fc_prs)
{
	return (fc_p_isascii(fc_prs) && p_isalpha(fc_prs)) ? 1 : 0;
}

static int fc_p_isurlchar(TParser *fc_prs)
{
	char		fc_ch;

	/* 不接受非 ASCII 符号 */
	if (fc_prs->state->charlen != 1)
		return 0;
	fc_ch = *(fc_prs->str + fc_prs->state->posbyte);
	/* 不允许空格或控制字符 */
	if (fc_ch <= 0x20 || fc_ch >= 0x7F)
		return 0;
	/* 拒绝 RFC 3986 不允许的字符 */
	switch (fc_ch)
	{
		case '"':
		case '<':
		case '>':
		case '\\':
		case '^':
		case '`':
		case '{':
		case '|':
		case '}':
			return 0;
	}
	return 1;
}


/* 有意抑制上述未使用函数的警告 */
void		_make_compiler_happy(void);
void _make_compiler_happy(void)
{
	p_isalnum(NULL);
	p_isnotalnum(NULL);
	p_isalpha(NULL);
	p_isnotalpha(NULL);
	p_isdigit(NULL);
	p_isnotdigit(NULL);
	p_islower(NULL);
	p_isnotlower(NULL);
	p_isprint(NULL);
	p_isnotprint(NULL);
	p_ispunct(NULL);
	p_isnotpunct(NULL);
	p_isspace(NULL);
	p_isnotspace(NULL);
	p_isupper(NULL);
	p_isnotupper(NULL);
	p_isxdigit(NULL);
	p_isnotxdigit(NULL);
	fc_p_isEOF(NULL);
	fc_p_iseqC(NULL);
	fc_p_isneC(NULL);
}


static void fc_SpecialTags(TParser *fc_prs)
{
	switch (fc_prs->state->lenchartoken)
	{
		case 8:					/* </script */
			if (pg_strncasecmp(fc_prs->token, "</script", 8) == 0)
				fc_prs->ignore = false;
			break;
		case 7:					/* <script || </style */
			if (pg_strncasecmp(fc_prs->token, "</style", 7) == 0)
				fc_prs->ignore = false;
			else if (pg_strncasecmp(fc_prs->token, "<script", 7) == 0)
				fc_prs->ignore = true;
			break;
		case 6:					/* <style */
			if (pg_strncasecmp(fc_prs->token, "<style", 6) == 0)
				fc_prs->ignore = true;
			break;
		default:
			break;
	}
}

static void fc_SpecialFURL(TParser *fc_prs)
{
	fc_prs->wanthost = true;
	fc_prs->state->posbyte -= fc_prs->state->lenbytetoken;
	fc_prs->state->poschar -= fc_prs->state->lenchartoken;
}

static void fc_SpecialHyphen(TParser *fc_prs)
{
	fc_prs->state->posbyte -= fc_prs->state->lenbytetoken;
	fc_prs->state->poschar -= fc_prs->state->lenchartoken;
}

static void fc_SpecialVerVersion(TParser *fc_prs)
{
	fc_prs->state->posbyte -= fc_prs->state->lenbytetoken;
	fc_prs->state->poschar -= fc_prs->state->lenchartoken;
	fc_prs->state->lenbytetoken = 0;
	fc_prs->state->lenchartoken = 0;
}

static int fc_p_isstophost(TParser *fc_prs)
{
	if (fc_prs->wanthost)
	{
		fc_prs->wanthost = false;
		return 1;
	}
	return 0;
}

static int fc_p_isignore(TParser *fc_prs)
{
	return (fc_prs->ignore) ? 1 : 0;
}

static int fc_p_ishost(TParser *fc_prs)
{
	TParser    *fc_tmpprs = fc_TParserCopyInit(fc_prs);
	int			fc_res = 0;

	fc_tmpprs->wanthost = true;

	/*
	 * 在递归之前检查堆栈深度。（由于 TParserGet() 不
	 * 通常递归，因此我们在这里而不是在那里放置检查的成本。）
	 */
	check_stack_depth();

	if (fc_TParserGet(fc_tmpprs) && fc_tmpprs->type == HOST)
	{
		fc_prs->state->posbyte += fc_tmpprs->lenbytetoken;
		fc_prs->state->poschar += fc_tmpprs->lenchartoken;
		fc_prs->state->lenbytetoken += fc_tmpprs->lenbytetoken;
		fc_prs->state->lenchartoken += fc_tmpprs->lenchartoken;
		fc_prs->state->charlen = fc_tmpprs->state->charlen;
		fc_res = 1;
	}
	fc_TParserCopyClose(fc_tmpprs);

	return fc_res;
}

static int fc_p_isURLPath(TParser *fc_prs)
{
	TParser    *fc_tmpprs = fc_TParserCopyInit(fc_prs);
	int			fc_res = 0;

	fc_tmpprs->state = fc_newTParserPosition(fc_tmpprs->state);
	fc_tmpprs->state->state = TPS_InURLPathFirst;

	/*
	 * 在递归之前检查堆栈深度。（由于 TParserGet() 不
	 * 通常递归，因此我们在这里而不是在那里放置检查的成本。）
	 */
	check_stack_depth();

	if (fc_TParserGet(fc_tmpprs) && fc_tmpprs->type == URLPATH)
	{
		fc_prs->state->posbyte += fc_tmpprs->lenbytetoken;
		fc_prs->state->poschar += fc_tmpprs->lenchartoken;
		fc_prs->state->lenbytetoken += fc_tmpprs->lenbytetoken;
		fc_prs->state->lenchartoken += fc_tmpprs->lenchartoken;
		fc_prs->state->charlen = fc_tmpprs->state->charlen;
		fc_res = 1;
	}
	fc_TParserCopyClose(fc_tmpprs);

	return fc_res;
}

/*
 * 如果当前字符的显示长度为零或
 * 它是几种语言中的特殊符号，则返回真。这样的字符
 * 不是单词断点，尽管它们不是isalpha。
 * 在单词的开头，它们不是它的一部分。
 */
static int fc_p_isspecial(TParser *fc_prs)
{
	/*
	 * pg_dsplen 可能返回 -1，这意味着错误或控制字符
	 */
	if (pg_dsplen(fc_prs->str + fc_prs->state->posbyte) == 0)
		return 1;

	/*
	 * “标记，间距组合”类别中的 Unicode 字符
	 * 这些字符不是字母，尽管它们也不是单词的断点。
	 * 仅在 utf 编码中检查，因为其他编码不是
	 * postgres 支持的，甚至不存在。
	 */
	if (GetDatabaseEncoding() == PG_UTF8 && fc_prs->usewide)
	{
		static const pg_wchar fc_strange_letter[] = {
			/*
			 * 使用二分搜索，因此元素应该是有序的
			 */
			0x0903,				/* 德文字符 SIGN VISARGA */
			0x093E,				/* 德文元音符号 AA */
			0x093F,				/* 德文元音符号 I */
			0x0940,				/* 德文元音符号 II */
			0x0949,				/* 德文元音符号 CANDRA O */
			0x094A,				/* 德文元音符号 SHORT O */
			0x094B,				/* 德文元音符号 O */
			0x094C,				/* 德文元音符号 AU */
			0x0982,				/* 孟加拉符号 ANUSVARA */
			0x0983,				/* 孟加拉符号 VISARGA */
			0x09BE,				/* 孟加拉元音符号 AA */
			0x09BF,				/* 孟加拉元音符号 I */
			0x09C0,				/* 孟加拉元音符号 II */
			0x09C7,				/* 孟加拉元音符号 E */
			0x09C8,				/* 孟加拉元音符号 AI */
			0x09CB,				/* 孟加拉元音符号 O */
			0x09CC,				/* 孟加拉元音符号 AU */
			0x09D7,				/* 孟加拉 AU 长度标记 */
			0x0A03,				/* 古尔穆克希符号 VISARGA */
			0x0A3E,				/* 古尔穆克希元音符号 AA */
			0x0A3F,				/* 古尔穆克希元音符号 I */
			0x0A40,				/* 古尔穆克希元音符号 II */
			0x0A83,				/* 古吉拉特语符号 VISARGA */
			0x0ABE,				/* 古吉拉特语元音符号 AA */
			0x0ABF,				/* 古吉拉特语元音符号 I */
			0x0AC0,				/* 古吉拉特语元音符号 II */
			0x0AC9,				/* 古吉拉特语元音符号 CANDRA O */
			0x0ACB,				/* 古吉拉特语元音符号 O */
			0x0ACC,				/* 古吉拉特语元音符号 AU */
			0x0B02,				/* 奥里亚符号 ANUSVARA */
			0x0B03,				/* 奥里亚符号 VISARGA */
			0x0B3E,				/* 奥里亚元音符号 AA */
			0x0B40,				/* 奥里亚元音符号 II */
			0x0B47,				/* 奥里亚元音符号 E */
			0x0B48,				/* 奥里亚元音符号 AI */
			0x0B4B,				/* 奥里亚元音符号 O */
			0x0B4C,				/* 奥里亚元音符号 AU */
			0x0B57,				/* 奥里亚 AU 长度标记 */
			0x0BBE,				/* 泰米尔元音符号 AA */
			0x0BBF,				/* 泰米尔元音符号 I */
			0x0BC1,				/* 泰米尔元音符号 U */
			0x0BC2,				/* 泰米尔元音符号 UU */
			0x0BC6,				/* 泰米尔元音符号 E */
			0x0BC7,				/* 泰米尔元音符号 EE */
			0x0BC8,				/* 泰米尔元音符号 AI */
			0x0BCA,				/* 泰米尔元音符号 O */
			0x0BCB,				/* 泰米尔元音符号 OO */
			0x0BCC,				/* 泰米尔元音符号 AU */
			0x0BD7,				/* 泰米尔 AU 长度标记 */
			0x0C01,				/* 泰卢固语符号 CANDRABINDU */
			0x0C02,				/* 泰卢固语符号 ANUSVARA */
			0x0C03,				/* 泰卢固语符号 VISARGA */
			0x0C41,				/* 泰卢固语元音符号 U */
			0x0C42,				/* 泰卢固语元音符号 UU */
			0x0C43,				/* 泰卢固语元音符号 VOCALIC R */
			0x0C44,				/* 泰卢固语元音符号 VOCALIC RR */
			0x0C82,				/* 卡纳达符号 ANUSVARA */
			0x0C83,				/* 卡纳达符号 VISARGA */
			0x0CBE,				/* 卡纳达元音符号 AA */
			0x0CC0,				/* 卡纳达元音符号 II */
			0x0CC1,				/* 卡纳达元音符号 U */
			0x0CC2,				/* 卡纳达元音符号 UU */
			0x0CC3,				/* 卡纳达元音符号 VOCALIC R */
			0x0CC4,				/* 卡纳达元音符号 VOCALIC RR */
			0x0CC7,				/* 卡纳达元音符号 EE */
			0x0CC8,				/* 卡纳达元音符号 AI */
			0x0CCA,				/* 卡纳达元音符号 O */
			0x0CCB,				/* 卡纳达元音符号 OO */
			0x0CD5,				/* 卡纳达长度标记 */
			0x0CD6,				/* 卡纳达 AI 长度标记 */
			0x0D02,				/* 马拉雅拉姆符号安斯瓦拉 */
			0x0D03,				/* 马拉雅拉姆符号维萨尔加 */
			0x0D3E,				/* 马拉雅拉姆元音符号AA */
			0x0D3F,				/* 马拉雅拉姆元音符号I */
			0x0D40,				/* 马拉雅拉姆元音符号II */
			0x0D46,				/* 马拉雅拉姆元音符号E */
			0x0D47,				/* 马拉雅拉姆元音符号EE */
			0x0D48,				/* 马拉雅拉姆元音符号AI */
			0x0D4A,				/* 马拉雅拉姆元音符号O */
			0x0D4B,				/* 马拉雅拉姆元音符号OO */
			0x0D4C,				/* 马拉雅拉姆元音符号AU */
			0x0D57,				/* 马拉雅拉姆AU长音标记 */
			0x0D82,				/* 僧伽罗符号安斯瓦拉亚 */
			0x0D83,				/* 僧伽罗符号维萨尔加亚 */
			0x0DCF,				/* 僧伽罗元音符号AELA-PILLA */
			0x0DD0,				/* 僧伽罗元音符号KETTI AEDA-PILLA */
			0x0DD1,				/* 僧伽罗元音符号DIGA AEDA-PILLA */
			0x0DD8,				/* 僧伽罗元音符号GAETTA-PILLA */
			0x0DD9,				/* 僧伽罗元音符号KOMBUVA */
			0x0DDA,				/* 僧伽罗元音符号DIGA KOMBUVA */
			0x0DDB,				/* 僧伽罗元音符号KOMBU DEKA */
			0x0DDC,				/* 僧伽罗元音符号KOMBUVA HAA AELA-PILLA */
			0x0DDD,				/* 僧伽罗元音符号KOMBUVA HAA DIGA AELA-PILLA */
			0x0DDE,				/* 僧伽罗元音符号KOMBUVA HAA GAYANUKITTA */
			0x0DDF,				/* 僧伽罗元音符号GAYANUKITTA */
			0x0DF2,				/* 僧伽罗元音符号DIGA GAETTA-PILLA */
			0x0DF3,				/* 僧伽罗元音符号DIGA GAYANUKITTA */
			0x0F3E,				/* 藏文符号YAR TSHES */
			0x0F3F,				/* 藏文符号MAR TSHES */
			0x0F7F,				/* 藏文符号RNAM BCAD */
			0x102B,				/* 缅甸元音符号TALL AA */
			0x102C,				/* 缅甸元音符号AA */
			0x1031,				/* 缅甸元音符号E */
			0x1038,				/* 缅甸符号维萨尔加 */
			0x103B,				/* 缅甸辅音符号中间YA */
			0x103C,				/* 缅甸辅音符号中间RA */
			0x1056,				/* 缅甸元音符号元音R */
			0x1057,				/* 缅甸元音符号元音RR */
			0x1062,				/* 缅甸元音符号SGAW KAREN EU */
			0x1063,				/* 缅甸音调标记SGAW KAREN HATHI */
			0x1064,				/* 缅甸音调标记SGAW KAREN KE PHO */
			0x1067,				/* 缅甸元音符号西方PWO KAREN EU */
			0x1068,				/* 缅甸元音符号西方PWO KAREN UE */
			0x1069,				/* 缅甸符号西方PWO KAREN TONE-1 */
			0x106A,				/* 缅甸符号西方PWO KAREN TONE-2 */
			0x106B,				/* 缅甸符号西方PWO KAREN TONE-3 */
			0x106C,				/* 缅甸符号西方PWO KAREN TONE-4 */
			0x106D,				/* 缅甸符号西方PWO KAREN TONE-5 */
			0x1083,				/* 缅甸元音符号SHAN AA */
			0x1084,				/* 缅甸元音符号SHAN E */
			0x1087,				/* 缅甸符号SHAN TONE-2 */
			0x1088,				/* 缅甸符号SHAN TONE-3 */
			0x1089,				/* 缅甸符号SHAN TONE-5 */
			0x108A,				/* 缅甸符号SHAN TONE-6 */
			0x108B,				/* 缅甸符号SHAN COUNCIL TONE-2 */
			0x108C,				/* 缅甸符号SHAN COUNCIL TONE-3 */
			0x108F,				/* 缅甸符号RUMAI PALAUNG TONE-5 */
			0x17B6,				/* 高棉元音符号AA */
			0x17BE,				/* 高棉元音符号OE */
			0x17BF,				/* 高棉元音符号YA */
			0x17C0,				/* 高棉元音符号IE */
			0x17C1,				/* 高棉元音符号E */
			0x17C2,				/* 高棉元音符号AE */
			0x17C3,				/* 高棉元音符号AI */
			0x17C4,				/* 高棉元音符号OO */
			0x17C5,				/* 高棉元音符号AU */
			0x17C7,				/* 高棉符号REAHMUK */
			0x17C8,				/* 高棉符号YUUKALEAPINTU */
			0x1923,				/* 林布元音符号EE */
			0x1924,				/* 林布元音符号AI */
			0x1925,				/* 林布元音符号OO */
			0x1926,				/* 林布元音符号AU */
			0x1929,				/* 林布附加字母YA */
			0x192A,				/* 林布附加字母RA */
			0x192B,				/* 林布附加字母WA */
			0x1930,				/* 林布小写字母KA */
			0x1931,				/* 林布小写字母NGA */
			0x1933,				/* LIMBU 小写字母 TA */
			0x1934,				/* LIMBU 小写字母 NA */
			0x1935,				/* LIMBU 小写字母 PA */
			0x1936,				/* LIMBU 小写字母 MA */
			0x1937,				/* LIMBU 小写字母 RA */
			0x1938,				/* LIMBU 小写字母 LA */
			0x19B0,				/* 新台鲁元音符号短元音符号 */
			0x19B1,				/* 新台鲁元音符号 AA */
			0x19B2,				/* 新台鲁元音符号 II */
			0x19B3,				/* 新台鲁元音符号 U */
			0x19B4,				/* 新台鲁元音符号 UU */
			0x19B5,				/* 新台鲁元音符号 E */
			0x19B6,				/* 新台鲁元音符号 AE */
			0x19B7,				/* 新台鲁元音符号 O */
			0x19B8,				/* 新台鲁元音符号 OA */
			0x19B9,				/* 新台鲁元音符号 UE */
			0x19BA,				/* 新台鲁元音符号 AY */
			0x19BB,				/* 新台鲁元音符号 AAY */
			0x19BC,				/* 新台鲁元音符号 UY */
			0x19BD,				/* 新台鲁元音符号 OY */
			0x19BE,				/* 新台鲁元音符号 OAY */
			0x19BF,				/* 新台鲁元音符号 UEY */
			0x19C0,				/* 新台鲁元音符号 IY */
			0x19C8,				/* 新台鲁声调符号-1 */
			0x19C9,				/* 新台鲁声调符号-2 */
			0x1A19,				/* 布吉语元音符号 E */
			0x1A1A,				/* 布吉语元音符号 O */
			0x1A1B,				/* 布吉语元音符号 AE */
			0x1B04,				/* 巴厘岛符号 BISAH */
			0x1B35,				/* 巴厘岛元音符号 TEDUNG */
			0x1B3B,				/* 巴厘岛元音符号 RA REPA TEDUNG */
			0x1B3D,				/* 巴厘岛元音符号 LA LENGA TEDUNG */
			0x1B3E,				/* 巴厘岛元音符号 TALING */
			0x1B3F,				/* 巴厘岛元音符号 TALING REPA */
			0x1B40,				/* 巴厘岛元音符号 TALING TEDUNG */
			0x1B41,				/* 巴厘岛元音符号 TALING REPA TEDUNG */
			0x1B43,				/* 巴厘岛元音符号 PEPET TEDUNG */
			0x1B44,				/* 巴厘岛 ADEG ADEG */
			0x1B82,				/* 巽他语符号 PANGWISAD */
			0x1BA1,				/* 巽他语辅音符号 PAMINGKAL */
			0x1BA6,				/* 巽他语元音符号 PANAELAENG */
			0x1BA7,				/* 巽他语元音符号 PANOLONG */
			0x1BAA,				/* 巽他语符号 PAMAAEH */
			0x1C24,				/* 莱普察附加字母 YA */
			0x1C25,				/* 莱普察附加字母 RA */
			0x1C26,				/* 莱普察元音符号 AA */
			0x1C27,				/* 莱普察元音符号 I */
			0x1C28,				/* 莱普察元音符号 O */
			0x1C29,				/* 莱普察元音符号 OO */
			0x1C2A,				/* 莱普察元音符号 U */
			0x1C2B,				/* 莱普察元音符号 UU */
			0x1C34,				/* 莱普察辅音符号 NYIN-DO */
			0x1C35,				/* 莱普察辅音符号 KANG */
			0xA823,				/* 西洛提纳格里元音符号 A */
			0xA824,				/* 西洛提纳格里元音符号 I */
			0xA827,				/* 西洛提纳格里元音符号 OO */
			0xA880,				/* 苏拉什特拉符号 ANUSVARA */
			0xA881,				/* 苏拉什特拉符号 VISARGA */
			0xA8B4,				/* 苏拉什特拉辅音符号 HAARU */
			0xA8B5,				/* 苏拉什特拉元音符号 AA */
			0xA8B6,				/* 苏拉什特拉元音符号 I */
			0xA8B7,				/* 苏拉什特拉元音符号 II */
			0xA8B8,				/* 苏拉什特拉元音符号 U */
			0xA8B9,				/* 苏拉什特拉元音符号 UU */
			0xA8BA,				/* 苏拉什特拉元音符号 VOCALIC R */
			0xA8BB,				/* 苏拉什特拉元音符号 VOCALIC RR */
			0xA8BC,				/* 苏拉什特拉元音符号 VOCALIC L */
			0xA8BD,				/* 苏拉什特拉元音符号 VOCALIC LL */
			0xA8BE,				/* 苏拉什特拉元音符号 E */
			0xA8BF,				/* 苏拉什特拉元音符号 EE */
			0xA8C0,				/* 苏拉什特拉元音符号 AI */
			0xA8C1,				/* 苏拉什特拉元音符号 O */
			0xA8C2,				/* 苏拉什特拉元音符号 OO */
			0xA8C3,				/* 苏拉什特拉元音符号 AU */
			0xA952,				/* 瑞江辅音符号 H */
			0xA953,				/* 瑞江省略符号 */
			0xAA2F,				/* 蔡族元音符号 O */
			0xAA30,				/* 蔡族元音符号 AI */
			0xAA33,				/* 蔡族辅音符号 YA */
			0xAA34,				/* 蔡族辅音符号 RA */
			0xAA4D				/* 蔡族辅音符号最终 H */
		};
		const pg_wchar *fc_StopLow = fc_strange_letter,
				   *fc_StopHigh = fc_strange_letter + lengthof(fc_strange_letter),
				   *fc_StopMiddle;
		pg_wchar	fc_c;

		if (fc_prs->pgwstr)
			fc_c = *(fc_prs->pgwstr + fc_prs->state->poschar);
		else
			fc_c = (pg_wchar) *(fc_prs->wstr + fc_prs->state->poschar);

		while (fc_StopLow < fc_StopHigh)
		{
			fc_StopMiddle = fc_StopLow + ((fc_StopHigh - fc_StopLow) >> 1);
			if (*fc_StopMiddle == fc_c)
				return 1;
			else if (*fc_StopMiddle < fc_c)
				fc_StopLow = fc_StopMiddle + 1;
			else
				fc_StopHigh = fc_StopMiddle;
		}
	}

	return 0;
}

/*
 * 解析器的状态/动作表
 */

static const TParserStateActionItem actionTPS_Base[] = {
	{fc_p_isEOF, 0, A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '<', A_PUSH, TPS_InTagFirst, 0, NULL},
	{fc_p_isignore, 0, A_NEXT, TPS_InSpace, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InAsciiWord, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InWord, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InUnsignedInt, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InSignedIntFirst, 0, NULL},
	{fc_p_iseqC, '+', A_PUSH, TPS_InSignedIntFirst, 0, NULL},
	{fc_p_iseqC, '&', A_PUSH, TPS_InXMLEntityFirst, 0, NULL},
	{fc_p_iseqC, '~', A_PUSH, TPS_InFileTwiddle, 0, NULL},
	{fc_p_iseqC, '/', A_PUSH, TPS_InFileFirst, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InPathFirstFirst, 0, NULL},
	{NULL, 0, A_NEXT, TPS_InSpace, 0, NULL}
};


static const TParserStateActionItem actionTPS_InNumWord[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, NUMWORD, NULL},
	{p_isalnum, 0, A_NEXT, TPS_InNumWord, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InNumWord, 0, NULL},
	{fc_p_iseqC, '@', A_PUSH, TPS_InEmail, 0, NULL},
	{fc_p_iseqC, '/', A_PUSH, TPS_InFileFirst, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InFileNext, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHyphenNumWordFirst, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, NUMWORD, NULL}
};

static const TParserStateActionItem actionTPS_InAsciiWord[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, ASCIIWORD, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InHostFirstDomain, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InFileNext, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHyphenAsciiWordFirst, 0, NULL},
	{fc_p_iseqC, '_', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '@', A_PUSH, TPS_InEmail, 0, NULL},
	{fc_p_iseqC, ':', A_PUSH, TPS_InProtocolFirst, 0, NULL},
	{fc_p_iseqC, '/', A_PUSH, TPS_InFileFirst, 0, NULL},
	{p_isdigit, 0, A_PUSH, TPS_InHost, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InNumWord, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InWord, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InWord, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, ASCIIWORD, NULL}
};

static const TParserStateActionItem actionTPS_InWord[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, WORD_T, NULL},
	{p_isalpha, 0, A_NEXT, TPS_Null, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InNumWord, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHyphenWordFirst, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, WORD_T, NULL}
};

static const TParserStateActionItem actionTPS_InUnsignedInt[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, UNSIGNEDINT, NULL},
	{p_isdigit, 0, A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InHostFirstDomain, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InUDecimalFirst, 0, NULL},
	{fc_p_iseqC, 'e', A_PUSH, TPS_InMantissaFirst, 0, NULL},
	{fc_p_iseqC, 'E', A_PUSH, TPS_InMantissaFirst, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '_', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '@', A_PUSH, TPS_InEmail, 0, NULL},
	{fc_p_isasclet, 0, A_PUSH, TPS_InHost, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InNumWord, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InNumWord, 0, NULL},
	{fc_p_iseqC, '/', A_PUSH, TPS_InFileFirst, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, UNSIGNEDINT, NULL}
};

static const TParserStateActionItem actionTPS_InSignedIntFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT | A_CLEAR, TPS_InSignedInt, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InSignedInt[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, SIGNEDINT, NULL},
	{p_isdigit, 0, A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InDecimalFirst, 0, NULL},
	{fc_p_iseqC, 'e', A_PUSH, TPS_InMantissaFirst, 0, NULL},
	{fc_p_iseqC, 'E', A_PUSH, TPS_InMantissaFirst, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, SIGNEDINT, NULL}
};

static const TParserStateActionItem actionTPS_InSpace[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, SPACE, NULL},
	{fc_p_iseqC, '<', A_BINGO, TPS_Base, SPACE, NULL},
	{fc_p_isignore, 0, A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '-', A_BINGO, TPS_Base, SPACE, NULL},
	{fc_p_iseqC, '+', A_BINGO, TPS_Base, SPACE, NULL},
	{fc_p_iseqC, '&', A_BINGO, TPS_Base, SPACE, NULL},
	{fc_p_iseqC, '/', A_BINGO, TPS_Base, SPACE, NULL},
	{p_isnotalnum, 0, A_NEXT, TPS_InSpace, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, SPACE, NULL}
};

static const TParserStateActionItem actionTPS_InUDecimalFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_CLEAR, TPS_InUDecimal, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InUDecimal[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, DECIMAL_T, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InUDecimal, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InVersionFirst, 0, NULL},
	{fc_p_iseqC, 'e', A_PUSH, TPS_InMantissaFirst, 0, NULL},
	{fc_p_iseqC, 'E', A_PUSH, TPS_InMantissaFirst, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, DECIMAL_T, NULL}
};

static const TParserStateActionItem actionTPS_InDecimalFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_CLEAR, TPS_InDecimal, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InDecimal[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, DECIMAL_T, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InDecimal, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InVerVersion, 0, NULL},
	{fc_p_iseqC, 'e', A_PUSH, TPS_InMantissaFirst, 0, NULL},
	{fc_p_iseqC, 'E', A_PUSH, TPS_InMantissaFirst, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, DECIMAL_T, NULL}
};

static const TParserStateActionItem actionTPS_InVerVersion[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_RERUN, TPS_InSVerVersion, 0, fc_SpecialVerVersion},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InSVerVersion[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_BINGO | A_CLRALL, TPS_InUnsignedInt, SPACE, NULL},
	{NULL, 0, A_NEXT, TPS_Null, 0, NULL}
};


static const TParserStateActionItem actionTPS_InVersionFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_CLEAR, TPS_InVersion, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InVersion[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, VERSIONNUMBER, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InVersion, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InVersionFirst, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, VERSIONNUMBER, NULL}
};

static const TParserStateActionItem actionTPS_InMantissaFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_CLEAR, TPS_InMantissa, 0, NULL},
	{fc_p_iseqC, '+', A_NEXT, TPS_InMantissaSign, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_InMantissaSign, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InMantissaSign[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_CLEAR, TPS_InMantissa, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InMantissa[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, SCIENTIFIC, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InMantissa, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, SCIENTIFIC, NULL}
};

static const TParserStateActionItem actionTPS_InXMLEntityFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '#', A_NEXT, TPS_InXMLEntityNumFirst, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InXMLEntity, 0, NULL},
	{fc_p_iseqC, ':', A_NEXT, TPS_InXMLEntity, 0, NULL},
	{fc_p_iseqC, '_', A_NEXT, TPS_InXMLEntity, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InXMLEntity[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isalnum, 0, A_NEXT, TPS_InXMLEntity, 0, NULL},
	{fc_p_iseqC, ':', A_NEXT, TPS_InXMLEntity, 0, NULL},
	{fc_p_iseqC, '_', A_NEXT, TPS_InXMLEntity, 0, NULL},
	{fc_p_iseqC, '.', A_NEXT, TPS_InXMLEntity, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_InXMLEntity, 0, NULL},
	{fc_p_iseqC, ';', A_NEXT, TPS_InXMLEntityEnd, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InXMLEntityNumFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, 'x', A_NEXT, TPS_InXMLEntityHexNumFirst, 0, NULL},
	{fc_p_iseqC, 'X', A_NEXT, TPS_InXMLEntityHexNumFirst, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InXMLEntityNum, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InXMLEntityHexNumFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isxdigit, 0, A_NEXT, TPS_InXMLEntityHexNum, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InXMLEntityNum[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InXMLEntityNum, 0, NULL},
	{fc_p_iseqC, ';', A_NEXT, TPS_InXMLEntityEnd, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InXMLEntityHexNum[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isxdigit, 0, A_NEXT, TPS_InXMLEntityHexNum, 0, NULL},
	{fc_p_iseqC, ';', A_NEXT, TPS_InXMLEntityEnd, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InXMLEntityEnd[] = {
	{NULL, 0, A_BINGO | A_CLEAR, TPS_Base, XMLENTITY, NULL}
};

static const TParserStateActionItem actionTPS_InTagFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '/', A_PUSH, TPS_InTagCloseFirst, 0, NULL},
	{fc_p_iseqC, '!', A_PUSH, TPS_InCommentFirst, 0, NULL},
	{fc_p_iseqC, '?', A_PUSH, TPS_InXMLBegin, 0, NULL},
	{fc_p_isasclet, 0, A_PUSH, TPS_InTagName, 0, NULL},
	{fc_p_iseqC, ':', A_PUSH, TPS_InTagName, 0, NULL},
	{fc_p_iseqC, '_', A_PUSH, TPS_InTagName, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InXMLBegin[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	/* <?xml ... */
	/* XXX 我们需要 m 和 l 的状态吗？现在这个接受 <?xZ */
	{fc_p_iseqC, 'x', A_NEXT, TPS_InTag, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InTagCloseFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InTagName, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InTagName[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	/* <br/> 情况 */
	{fc_p_iseqC, '/', A_NEXT, TPS_InTagBeginEnd, 0, NULL},
	{fc_p_iseqC, '>', A_NEXT, TPS_InTagEnd, 0, fc_SpecialTags},
	{p_isspace, 0, A_NEXT, TPS_InTag, 0, fc_SpecialTags},
	{p_isalnum, 0, A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, ':', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '_', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '.', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_Null, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InTagBeginEnd[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '>', A_NEXT, TPS_InTagEnd, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InTag[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '>', A_NEXT, TPS_InTagEnd, 0, fc_SpecialTags},
	{fc_p_iseqC, '\'', A_NEXT, TPS_InTagEscapeK, 0, NULL},
	{fc_p_iseqC, '"', A_NEXT, TPS_InTagEscapeKK, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '=', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '_', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '#', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '/', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, ':', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '.', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '&', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '?', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '%', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '~', A_NEXT, TPS_Null, 0, NULL},
	{p_isspace, 0, A_NEXT, TPS_Null, 0, fc_SpecialTags},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InTagEscapeK[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '\\', A_PUSH, TPS_InTagBackSleshed, 0, NULL},
	{fc_p_iseqC, '\'', A_NEXT, TPS_InTag, 0, NULL},
	{NULL, 0, A_NEXT, TPS_InTagEscapeK, 0, NULL}
};

static const TParserStateActionItem actionTPS_InTagEscapeKK[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '\\', A_PUSH, TPS_InTagBackSleshed, 0, NULL},
	{fc_p_iseqC, '"', A_NEXT, TPS_InTag, 0, NULL},
	{NULL, 0, A_NEXT, TPS_InTagEscapeKK, 0, NULL}
};

static const TParserStateActionItem actionTPS_InTagBackSleshed[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{NULL, 0, A_MERGE, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InTagEnd[] = {
	{NULL, 0, A_BINGO | A_CLRALL, TPS_Base, TAG_T, NULL}
};

static const TParserStateActionItem actionTPS_InCommentFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_InCommentLast, 0, NULL},
	/* <!DOCTYPE ...> */
	{fc_p_iseqC, 'D', A_NEXT, TPS_InTag, 0, NULL},
	{fc_p_iseqC, 'd', A_NEXT, TPS_InTag, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InCommentLast[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_InComment, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InComment[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_InCloseCommentFirst, 0, NULL},
	{NULL, 0, A_NEXT, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InCloseCommentFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_InCloseCommentLast, 0, NULL},
	{NULL, 0, A_NEXT, TPS_InComment, 0, NULL}
};

static const TParserStateActionItem actionTPS_InCloseCommentLast[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_Null, 0, NULL},
	{fc_p_iseqC, '>', A_NEXT, TPS_InCommentEnd, 0, NULL},
	{NULL, 0, A_NEXT, TPS_InComment, 0, NULL}
};

static const TParserStateActionItem actionTPS_InCommentEnd[] = {
	{NULL, 0, A_BINGO | A_CLRALL, TPS_Base, TAG_T, NULL}
};

static const TParserStateActionItem actionTPS_InHostFirstDomain[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHostDomainSecond, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHost, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InHostDomainSecond[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHostDomain, 0, NULL},
	{p_isdigit, 0, A_PUSH, TPS_InHost, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '_', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InHostFirstDomain, 0, NULL},
	{fc_p_iseqC, '@', A_PUSH, TPS_InEmail, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InHostDomain[] = {
	{fc_p_isEOF, 0, A_BINGO | A_CLRALL, TPS_Base, HOST, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHostDomain, 0, NULL},
	{p_isdigit, 0, A_PUSH, TPS_InHost, 0, NULL},
	{fc_p_iseqC, ':', A_PUSH, TPS_InPortFirst, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '_', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InHostFirstDomain, 0, NULL},
	{fc_p_iseqC, '@', A_PUSH, TPS_InEmail, 0, NULL},
	{p_isdigit, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isstophost, 0, A_BINGO | A_CLRALL, TPS_InURLPathStart, HOST, NULL},
	{fc_p_iseqC, '/', A_PUSH, TPS_InFURL, 0, NULL},
	{NULL, 0, A_BINGO | A_CLRALL, TPS_Base, HOST, NULL}
};

static const TParserStateActionItem actionTPS_InPortFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InPort, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InPort[] = {
	{fc_p_isEOF, 0, A_BINGO | A_CLRALL, TPS_Base, HOST, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InPort, 0, NULL},
	{fc_p_isstophost, 0, A_BINGO | A_CLRALL, TPS_InURLPathStart, HOST, NULL},
	{fc_p_iseqC, '/', A_PUSH, TPS_InFURL, 0, NULL},
	{NULL, 0, A_BINGO | A_CLRALL, TPS_Base, HOST, NULL}
};

static const TParserStateActionItem actionTPS_InHostFirstAN[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHost, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHost, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InHost[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHost, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHost, 0, NULL},
	{fc_p_iseqC, '@', A_PUSH, TPS_InEmail, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InHostFirstDomain, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{fc_p_iseqC, '_', A_PUSH, TPS_InHostFirstAN, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InEmail[] = {
	{fc_p_isstophost, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_ishost, 0, A_BINGO | A_CLRALL, TPS_Base, EMAIL, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InFileFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InFile, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '.', A_NEXT, TPS_InPathFirst, 0, NULL},
	{fc_p_iseqC, '_', A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '~', A_PUSH, TPS_InFileTwiddle, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InFileTwiddle[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InFile, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '_', A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '/', A_NEXT, TPS_InFileFirst, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InPathFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InFile, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '_', A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '.', A_NEXT, TPS_InPathSecond, 0, NULL},
	{fc_p_iseqC, '/', A_NEXT, TPS_InFileFirst, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InPathFirstFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '.', A_NEXT, TPS_InPathSecond, 0, NULL},
	{fc_p_iseqC, '/', A_NEXT, TPS_InFileFirst, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InPathSecond[] = {
	{fc_p_isEOF, 0, A_BINGO | A_CLEAR, TPS_Base, FILEPATH, NULL},
	{fc_p_iseqC, '/', A_NEXT | A_PUSH, TPS_InFileFirst, 0, NULL},
	{fc_p_iseqC, '/', A_BINGO | A_CLEAR, TPS_Base, FILEPATH, NULL},
	{p_isspace, 0, A_BINGO | A_CLEAR, TPS_Base, FILEPATH, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InFile[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, FILEPATH, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InFile, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '.', A_PUSH, TPS_InFileNext, 0, NULL},
	{fc_p_iseqC, '_', A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '-', A_NEXT, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '/', A_PUSH, TPS_InFileFirst, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, FILEPATH, NULL}
};

static const TParserStateActionItem actionTPS_InFileNext[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isasclet, 0, A_CLEAR, TPS_InFile, 0, NULL},
	{p_isdigit, 0, A_CLEAR, TPS_InFile, 0, NULL},
	{fc_p_iseqC, '_', A_CLEAR, TPS_InFile, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InURLPathFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isurlchar, 0, A_NEXT, TPS_InURLPath, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL},
};

static const TParserStateActionItem actionTPS_InURLPathStart[] = {
	{NULL, 0, A_NEXT, TPS_InURLPath, 0, NULL}
};

static const TParserStateActionItem actionTPS_InURLPath[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, URLPATH, NULL},
	{fc_p_isurlchar, 0, A_NEXT, TPS_InURLPath, 0, NULL},
	{NULL, 0, A_BINGO, TPS_Base, URLPATH, NULL}
};

static const TParserStateActionItem actionTPS_InFURL[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isURLPath, 0, A_BINGO | A_CLRALL, TPS_Base, URL_T, fc_SpecialFURL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InProtocolFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '/', A_NEXT, TPS_InProtocolSecond, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InProtocolSecond[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_iseqC, '/', A_NEXT, TPS_InProtocolEnd, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InProtocolEnd[] = {
	{NULL, 0, A_BINGO | A_CLRALL, TPS_Base, PROTOCOL, NULL}
};

static const TParserStateActionItem actionTPS_InHyphenAsciiWordFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHyphenAsciiWord, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenWord, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHyphenDigitLookahead, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InHyphenAsciiWord[] = {
	{fc_p_isEOF, 0, A_BINGO | A_CLRALL, TPS_InParseHyphen, ASCIIHWORD, fc_SpecialHyphen},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHyphenAsciiWord, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenWord, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InHyphenWord, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHyphenNumWord, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHyphenAsciiWordFirst, 0, NULL},
	{NULL, 0, A_BINGO | A_CLRALL, TPS_InParseHyphen, ASCIIHWORD, fc_SpecialHyphen}
};

static const TParserStateActionItem actionTPS_InHyphenWordFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenWord, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHyphenDigitLookahead, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InHyphenWord[] = {
	{fc_p_isEOF, 0, A_BINGO | A_CLRALL, TPS_InParseHyphen, HWORD, fc_SpecialHyphen},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenWord, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InHyphenWord, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHyphenNumWord, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHyphenWordFirst, 0, NULL},
	{NULL, 0, A_BINGO | A_CLRALL, TPS_InParseHyphen, HWORD, fc_SpecialHyphen}
};

static const TParserStateActionItem actionTPS_InHyphenNumWordFirst[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenNumWord, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHyphenDigitLookahead, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InHyphenNumWord[] = {
	{fc_p_isEOF, 0, A_BINGO | A_CLRALL, TPS_InParseHyphen, NUMHWORD, fc_SpecialHyphen},
	{p_isalnum, 0, A_NEXT, TPS_InHyphenNumWord, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InHyphenNumWord, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InHyphenNumWordFirst, 0, NULL},
	{NULL, 0, A_BINGO | A_CLRALL, TPS_InParseHyphen, NUMHWORD, fc_SpecialHyphen}
};

static const TParserStateActionItem actionTPS_InHyphenDigitLookahead[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHyphenDigitLookahead, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenNumWord, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InHyphenNumWord, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InParseHyphen[] = {
	{fc_p_isEOF, 0, A_RERUN, TPS_Base, 0, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHyphenAsciiWordPart, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenWordPart, 0, NULL},
	{p_isdigit, 0, A_PUSH, TPS_InHyphenUnsignedInt, 0, NULL},
	{fc_p_iseqC, '-', A_PUSH, TPS_InParseHyphenHyphen, 0, NULL},
	{NULL, 0, A_RERUN, TPS_Base, 0, NULL}
};

static const TParserStateActionItem actionTPS_InParseHyphenHyphen[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isalnum, 0, A_BINGO | A_CLEAR, TPS_InParseHyphen, SPACE, NULL},
	{fc_p_isspecial, 0, A_BINGO | A_CLEAR, TPS_InParseHyphen, SPACE, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};

static const TParserStateActionItem actionTPS_InHyphenWordPart[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, PARTHWORD, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenWordPart, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InHyphenWordPart, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHyphenNumWordPart, 0, NULL},
	{NULL, 0, A_BINGO, TPS_InParseHyphen, PARTHWORD, NULL}
};

static const TParserStateActionItem actionTPS_InHyphenAsciiWordPart[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, ASCIIPARTHWORD, NULL},
	{fc_p_isasclet, 0, A_NEXT, TPS_InHyphenAsciiWordPart, 0, NULL},
	{p_isalpha, 0, A_NEXT, TPS_InHyphenWordPart, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InHyphenWordPart, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_InHyphenNumWordPart, 0, NULL},
	{NULL, 0, A_BINGO, TPS_InParseHyphen, ASCIIPARTHWORD, NULL}
};

static const TParserStateActionItem actionTPS_InHyphenNumWordPart[] = {
	{fc_p_isEOF, 0, A_BINGO, TPS_Base, NUMPARTHWORD, NULL},
	{p_isalnum, 0, A_NEXT, TPS_InHyphenNumWordPart, 0, NULL},
	{fc_p_isspecial, 0, A_NEXT, TPS_InHyphenNumWordPart, 0, NULL},
	{NULL, 0, A_BINGO, TPS_InParseHyphen, NUMPARTHWORD, NULL}
};

static const TParserStateActionItem actionTPS_InHyphenUnsignedInt[] = {
	{fc_p_isEOF, 0, A_POP, TPS_Null, 0, NULL},
	{p_isdigit, 0, A_NEXT, TPS_Null, 0, NULL},
	{p_isalpha, 0, A_CLEAR, TPS_InHyphenNumWordPart, 0, NULL},
	{fc_p_isspecial, 0, A_CLEAR, TPS_InHyphenNumWordPart, 0, NULL},
	{NULL, 0, A_POP, TPS_Null, 0, NULL}
};


/*
 * 每个状态解析器动作的主表
 */
typedef struct
{
	const TParserStateActionItem *action;	/* 实际状态信息 */
	TParserState state;			/* 仅用于 Assert 交叉检查 */
#ifdef WPARSER_TRACE
	const char *state_name;		/* 仅用于调试输出 */
#endif
} TParserStateAction;

#ifdef WPARSER_TRACE
#define TPARSERSTATEACTION(state) \
	{ CppConcat(action,state), state, CppAsString(state) }
#else
#define TPARSERSTATEACTION(state) \
	{ CppConcat(action,state), state }
#endif

/*
 * 顺序必须与 typedef enum {} TParserState 中相同！！
 */

static const TParserStateAction Actions[] = {
	TPARSERSTATEACTION(TPS_Base),
	TPARSERSTATEACTION(TPS_InNumWord),
	TPARSERSTATEACTION(TPS_InAsciiWord),
	TPARSERSTATEACTION(TPS_InWord),
	TPARSERSTATEACTION(TPS_InUnsignedInt),
	TPARSERSTATEACTION(TPS_InSignedIntFirst),
	TPARSERSTATEACTION(TPS_InSignedInt),
	TPARSERSTATEACTION(TPS_InSpace),
	TPARSERSTATEACTION(TPS_InUDecimalFirst),
	TPARSERSTATEACTION(TPS_InUDecimal),
	TPARSERSTATEACTION(TPS_InDecimalFirst),
	TPARSERSTATEACTION(TPS_InDecimal),
	TPARSERSTATEACTION(TPS_InVerVersion),
	TPARSERSTATEACTION(TPS_InSVerVersion),
	TPARSERSTATEACTION(TPS_InVersionFirst),
	TPARSERSTATEACTION(TPS_InVersion),
	TPARSERSTATEACTION(TPS_InMantissaFirst),
	TPARSERSTATEACTION(TPS_InMantissaSign),
	TPARSERSTATEACTION(TPS_InMantissa),
	TPARSERSTATEACTION(TPS_InXMLEntityFirst),
	TPARSERSTATEACTION(TPS_InXMLEntity),
	TPARSERSTATEACTION(TPS_InXMLEntityNumFirst),
	TPARSERSTATEACTION(TPS_InXMLEntityNum),
	TPARSERSTATEACTION(TPS_InXMLEntityHexNumFirst),
	TPARSERSTATEACTION(TPS_InXMLEntityHexNum),
	TPARSERSTATEACTION(TPS_InXMLEntityEnd),
	TPARSERSTATEACTION(TPS_InTagFirst),
	TPARSERSTATEACTION(TPS_InXMLBegin),
	TPARSERSTATEACTION(TPS_InTagCloseFirst),
	TPARSERSTATEACTION(TPS_InTagName),
	TPARSERSTATEACTION(TPS_InTagBeginEnd),
	TPARSERSTATEACTION(TPS_InTag),
	TPARSERSTATEACTION(TPS_InTagEscapeK),
	TPARSERSTATEACTION(TPS_InTagEscapeKK),
	TPARSERSTATEACTION(TPS_InTagBackSleshed),
	TPARSERSTATEACTION(TPS_InTagEnd),
	TPARSERSTATEACTION(TPS_InCommentFirst),
	TPARSERSTATEACTION(TPS_InCommentLast),
	TPARSERSTATEACTION(TPS_InComment),
	TPARSERSTATEACTION(TPS_InCloseCommentFirst),
	TPARSERSTATEACTION(TPS_InCloseCommentLast),
	TPARSERSTATEACTION(TPS_InCommentEnd),
	TPARSERSTATEACTION(TPS_InHostFirstDomain),
	TPARSERSTATEACTION(TPS_InHostDomainSecond),
	TPARSERSTATEACTION(TPS_InHostDomain),
	TPARSERSTATEACTION(TPS_InPortFirst),
	TPARSERSTATEACTION(TPS_InPort),
	TPARSERSTATEACTION(TPS_InHostFirstAN),
	TPARSERSTATEACTION(TPS_InHost),
	TPARSERSTATEACTION(TPS_InEmail),
	TPARSERSTATEACTION(TPS_InFileFirst),
	TPARSERSTATEACTION(TPS_InFileTwiddle),
	TPARSERSTATEACTION(TPS_InPathFirst),
	TPARSERSTATEACTION(TPS_InPathFirstFirst),
	TPARSERSTATEACTION(TPS_InPathSecond),
	TPARSERSTATEACTION(TPS_InFile),
	TPARSERSTATEACTION(TPS_InFileNext),
	TPARSERSTATEACTION(TPS_InURLPathFirst),
	TPARSERSTATEACTION(TPS_InURLPathStart),
	TPARSERSTATEACTION(TPS_InURLPath),
	TPARSERSTATEACTION(TPS_InFURL),
	TPARSERSTATEACTION(TPS_InProtocolFirst),
	TPARSERSTATEACTION(TPS_InProtocolSecond),
	TPARSERSTATEACTION(TPS_InProtocolEnd),
	TPARSERSTATEACTION(TPS_InHyphenAsciiWordFirst),
	TPARSERSTATEACTION(TPS_InHyphenAsciiWord),
	TPARSERSTATEACTION(TPS_InHyphenWordFirst),
	TPARSERSTATEACTION(TPS_InHyphenWord),
	TPARSERSTATEACTION(TPS_InHyphenNumWordFirst),
	TPARSERSTATEACTION(TPS_InHyphenNumWord),
	TPARSERSTATEACTION(TPS_InHyphenDigitLookahead),
	TPARSERSTATEACTION(TPS_InParseHyphen),
	TPARSERSTATEACTION(TPS_InParseHyphenHyphen),
	TPARSERSTATEACTION(TPS_InHyphenWordPart),
	TPARSERSTATEACTION(TPS_InHyphenAsciiWordPart),
	TPARSERSTATEACTION(TPS_InHyphenNumWordPart),
	TPARSERSTATEACTION(TPS_InHyphenUnsignedInt)
};


static bool fc_TParserGet(TParser *fc_prs)
{
	const TParserStateActionItem *fc_item = NULL;

	CHECK_FOR_INTERRUPTS();

	Assert(fc_prs->state);

	if (fc_prs->state->posbyte >= fc_prs->lenstr)
		return false;

	fc_prs->token = fc_prs->str + fc_prs->state->posbyte;
	fc_prs->state->pushedAtAction = NULL;

	/* 查看字符串 */
	while (fc_prs->state->posbyte <= fc_prs->lenstr)
	{
		if (fc_prs->state->posbyte == fc_prs->lenstr)
			fc_prs->state->charlen = 0;
		else
			fc_prs->state->charlen = (fc_prs->charmaxlen == 1) ? fc_prs->charmaxlen :
				pg_mblen(fc_prs->str + fc_prs->state->posbyte);

		Assert(fc_prs->state->posbyte + fc_prs->state->charlen <= fc_prs->lenstr);
		Assert(fc_prs->state->state >= TPS_Base && fc_prs->state->state < TPS_Null);
		Assert(Actions[fc_prs->state->state].state == fc_prs->state->state);

		if (fc_prs->state->pushedAtAction)
		{
			/* 在 POP 后，从下一个测试开始 */
			fc_item = fc_prs->state->pushedAtAction + 1;
			fc_prs->state->pushedAtAction = NULL;
		}
		else
		{
			fc_item = Actions[fc_prs->state->state].action;
			Assert(fc_item != NULL);
		}

		/* 通过字符类查找动作 */
		while (fc_item->isclass)
		{
			fc_prs->c = fc_item->c;
			if (fc_item->isclass(fc_prs) != 0)
				break;
			fc_item++;
		}

#ifdef WPARSER_TRACE
		{
			TParserPosition *fc_ptr;

			fprintf(stderr, "state ");
			/* 根据堆栈深度缩进 */
			for (fc_ptr = fc_prs->state->prev; fc_ptr; fc_ptr = fc_ptr->prev)
				fprintf(stderr, "  ");
			fprintf(stderr, "%s ", Actions[fc_prs->state->state].state_name);
			if (fc_prs->state->posbyte < fc_prs->lenstr)
				fprintf(stderr, "at %c", *(fc_prs->str + fc_prs->state->posbyte));
			else
				fprintf(stderr, "at EOF");
			fprintf(stderr, " matched rule %d flags%s%s%s%s%s%s%s%s%s%s%s\n",
					(int) (fc_item - Actions[fc_prs->state->state].action),
					(fc_item->flags & A_BINGO) ? " BINGO" : "",
					(fc_item->flags & A_POP) ? " POP" : "",
					(fc_item->flags & A_PUSH) ? " PUSH" : "",
					(fc_item->flags & A_RERUN) ? " RERUN" : "",
					(fc_item->flags & A_CLEAR) ? " CLEAR" : "",
					(fc_item->flags & A_MERGE) ? " MERGE" : "",
					(fc_item->flags & A_CLRALL) ? " CLRALL" : "",
					(fc_item->tostate != TPS_Null) ? " tostate " : "",
					(fc_item->tostate != TPS_Null) ? Actions[fc_item->tostate].state_name : "",
					(fc_item->type > 0) ? " type " : "",
					tok_alias[fc_item->type]);
		}
#endif

		/* 如果存在，调用特殊处理程序 */
		if (fc_item->special)
			fc_item->special(fc_prs);

		/* BINGO，令牌被找到 */
		if (fc_item->flags & A_BINGO)
		{
			Assert(fc_item->type > 0);
			fc_prs->lenbytetoken = fc_prs->state->lenbytetoken;
			fc_prs->lenchartoken = fc_prs->state->lenchartoken;
			fc_prs->state->lenbytetoken = fc_prs->state->lenchartoken = 0;
			fc_prs->type = fc_item->type;
		}

		/* 根据标志执行各种操作 */
		if (fc_item->flags & A_POP)
		{						/* 在堆栈中弹出存储的状态 */
			TParserPosition *fc_ptr = fc_prs->state->prev;

			pfree(fc_prs->state);
			fc_prs->state = fc_ptr;
			Assert(fc_prs->state);
		}
		else if (fc_item->flags & A_PUSH)
		{						/* 在堆栈中推送（存储）状态 */
			fc_prs->state->pushedAtAction = fc_item;	/* 记住我们推送的位置 */
			fc_prs->state = fc_newTParserPosition(fc_prs->state);
		}
		else if (fc_item->flags & A_CLEAR)
		{						/* 清除之前推送的状态 */
			TParserPosition *fc_ptr;

			Assert(fc_prs->state->prev);
			fc_ptr = fc_prs->state->prev->prev;
			pfree(fc_prs->state->prev);
			fc_prs->state->prev = fc_ptr;
		}
		else if (fc_item->flags & A_CLRALL)
		{						/* 清除所有之前推送的状态 */
			TParserPosition *fc_ptr;

			while (fc_prs->state->prev)
			{
				fc_ptr = fc_prs->state->prev->prev;
				pfree(fc_prs->state->prev);
				fc_prs->state->prev = fc_ptr;
			}
		}
		else if (fc_item->flags & A_MERGE)
		{						/* 将 posinfo 与当前状态和推送状态合并 */
			TParserPosition *fc_ptr = fc_prs->state;

			Assert(fc_prs->state->prev);
			fc_prs->state = fc_prs->state->prev;

			fc_prs->state->posbyte = fc_ptr->posbyte;
			fc_prs->state->poschar = fc_ptr->poschar;
			fc_prs->state->charlen = fc_ptr->charlen;
			fc_prs->state->lenbytetoken = fc_ptr->lenbytetoken;
			fc_prs->state->lenchartoken = fc_ptr->lenchartoken;
			pfree(fc_ptr);
		}

		/* 如果指向，则设置新状态 */
		if (fc_item->tostate != TPS_Null)
			fc_prs->state->state = fc_item->tostate;

		/* 检查是否要退出 */
		if ((fc_item->flags & A_BINGO) ||
			(fc_prs->state->posbyte >= fc_prs->lenstr &&
			 (fc_item->flags & A_RERUN) == 0))
			break;

		/* 如果我们应该重新运行或者只是恢复状态，则返回循环的开始 */
		if (fc_item->flags & (A_RERUN | A_POP))
			continue;

		/* 向前移动 */
		if (fc_prs->state->charlen)
		{
			fc_prs->state->posbyte += fc_prs->state->charlen;
			fc_prs->state->lenbytetoken += fc_prs->state->charlen;
			fc_prs->state->poschar++;
			fc_prs->state->lenchartoken++;
		}
	}

	return (fc_item && (fc_item->flags & A_BINGO));
}

Datum prsd_lextype(PG_FUNCTION_ARGS)
{
	LexDescr   *fc_descr = (LexDescr *) palloc(sizeof(LexDescr) * (LASTNUM + 1));
	int			fc_i;

	for (fc_i = 1; fc_i <= LASTNUM; fc_i++)
	{
		fc_descr[fc_i - 1].lexid = fc_i;
		fc_descr[fc_i - 1].alias = pstrdup(tok_alias[fc_i]);
		fc_descr[fc_i - 1].descr = pstrdup(lex_descr[fc_i]);
	}

	fc_descr[LASTNUM].lexid = 0;

	PG_RETURN_POINTER(fc_descr);
}

Datum prsd_start(PG_FUNCTION_ARGS)
{
	PG_RETURN_POINTER(fc_TParserInit((char *) PG_GETARG_POINTER(0), PG_GETARG_INT32(1)));
}

Datum prsd_nexttoken(PG_FUNCTION_ARGS)
{
	TParser    *fc_p = (TParser *) PG_GETARG_POINTER(0);
	char	  **fc_t = (char **) PG_GETARG_POINTER(1);
	int		   *fc_tlen = (int *) PG_GETARG_POINTER(2);

	if (!fc_TParserGet(fc_p))
		PG_RETURN_INT32(0);

	*fc_t = fc_p->token;
	*fc_tlen = fc_p->lenbytetoken;

	PG_RETURN_INT32(fc_p->type);
}

Datum prsd_end(PG_FUNCTION_ARGS)
{
	TParser    *fc_p = (TParser *) PG_GETARG_POINTER(0);

	fc_TParserClose(fc_p);
	PG_RETURN_VOID();
}


/*
 * ts_headline 支持从这里开始
 */

/* 令牌类型分类宏 */
#define TS_IDIGNORE(x)	( (x)==TAG_T || (x)==PROTOCOL || (x)==SPACE || (x)==XMLENTITY )
#define HLIDREPLACE(x)	( (x)==TAG_T )
#define HLIDSKIP(x)		( (x)==URL_T || (x)==NUMHWORD || (x)==ASCIIHWORD || (x)==HWORD )
#define XMLHLIDSKIP(x)	( (x)==URL_T || (x)==NUMHWORD || (x)==ASCIIHWORD || (x)==HWORD )
#define NONWORDTOKEN(x) ( (x)==SPACE || HLIDREPLACE(x) || HLIDSKIP(x) )
#define NOENDTOKEN(x)	( NONWORDTOKEN(x) || (x)==SCIENTIFIC || (x)==VERSIONNUMBER || (x)==DECIMAL_T || (x)==SIGNEDINT || (x)==UNSIGNEDINT || TS_IDIGNORE(x) )

/*
 * 在标题选择中有用的宏。这些依赖于可用性
 * "HeadlineParsedText *prs" 来描述某些文本，以及 "int shortword"
 * 来描述 "短单词" 长度参数。
 */

/* 有趣的单词是未重复的搜索词 */
#define INTERESTINGWORD(j) \
	(fc_prs->words[j].item && !fc_prs->words[j].repeated)

/* 不想在非单词或短单词上结束，除非有趣 */
#define BADENDPOINT(j) \
	((NOENDTOKEN(fc_prs->words[j].type) || fc_prs->words[j].len <= fc_shortword) && \
	 !INTERESTINGWORD(j))

typedef struct
{
	/* 一个覆盖（实际上是一个片段）用于 mark_hl_fragments */
	int32		startpos;		/* 片段的起始单词索引 */
	int32		endpos;			/* 结束单词索引（包含） */
	int32		poslen;			/* 有趣词的数量 */
	int32		curlen;			/* 单词的总数 */
	bool		chosen;			/* 选中？ */
	bool		excluded;		/* 排除？ */
} CoverPos;

typedef struct
{
	/* checkcondition_HL 的回调数据 */
	HeadlineWordEntry *words;
	int			len;
} hlCheck;


/*
 * TS_execute 回调，用于将 tsquery 操作数匹配到标题单词
 */
static TSTernaryValue fc_checkcondition_HL(void *fc_opaque, QueryOperand *fc_val, ExecPhraseData *fc_data)
{
	hlCheck    *fc_checkval = (hlCheck *) fc_opaque;
	int			fc_i;

	/* 扫描单词数组以匹配项目 */
	for (fc_i = 0; fc_i < fc_checkval->len; fc_i++)
	{
		if (fc_checkval->words[fc_i].item == fc_val)
		{
			/* 如果数据 == NULL，则不需要报告位置 */
			if (!fc_data)
				return TS_YES;

			if (!fc_data->pos)
			{
				fc_data->pos = palloc(sizeof(WordEntryPos) * fc_checkval->len);
				fc_data->allocated = true;
				fc_data->npos = 1;
				fc_data->pos[0] = fc_checkval->words[fc_i].pos;
			}
			else if (fc_data->pos[fc_data->npos - 1] < fc_checkval->words[fc_i].pos)
			{
				fc_data->pos[fc_data->npos++] = fc_checkval->words[fc_i].pos;
			}
		}
	}

	if (fc_data && fc_data->npos > 0)
		return TS_YES;

	return TS_NO;
}

/*
 * hlFirstIndex: 查找第一个索引 >= pos，包含查询中使用的任何单词
 *
 * 如果没有这样的索引，则返回 -1
 */
static int fc_hlFirstIndex(HeadlineParsedText *fc_prs, int fc_pos)
{
	int			fc_i;

	for (fc_i = fc_pos; fc_i < fc_prs->curwords; fc_i++)
	{
		if (fc_prs->words[fc_i].item != NULL)
			return fc_i;
	}
	return -1;
}

/*
 * hlCover: 尝试找到满足查询的 prs 的单词列表的子字符串
 *
 * 进入时，*p 必须是要考虑的第一个单词索引（将其初始化为零，或在之前成功搜索之后的下一个索引）。
 * 我们将考虑从该单词开始或之后的所有子字符串，并且包含不超过 max_cover 个单词。 （我们需要长度限制，以避免在长文档中出现 O(N^2) 的时间复杂度，文档中有许多查询单词但完全匹配的很少。实际上，由于 checkcondition_HL 在被检查的子字符串长度上大致为 O(N)，所以情况更糟。）
 *
 * 成功时，设置 *p 为第一个单词索引，*q 为覆盖子字符串的最后一个单词索引，并返回 true。
 *
 * 结果是一个最小覆盖，意味着 *p 和 *q 都将是查询中使用的单词。
 */
static bool fc_hlCover(HeadlineParsedText *fc_prs, TSQuery fc_query, int fc_max_cover,
		int *fc_p, int *fc_q)
{
	int			fc_pmin,
				fc_pmax,
				fc_nextpmin,
				fc_nextpmax;
	hlCheck		fc_ch;

	if (fc_query->size <= 0)
		return false;			/* 空查询匹配没有内容 */

	/*
	 * 我们寻找满足查询的 prs->words 中最早、最短的子字符串。 pmin 和 pmax 索引必须是出现在查询中的单词；在这些点之间尝试端点是没有意义的。
	 */
	fc_pmin = fc_hlFirstIndex(fc_prs, *fc_p);
	while (fc_pmin >= 0)
	{
		/* 这个无用的赋值只是让愚蠢的编译器安静 */
		fc_nextpmin = -1;
		/* 考虑从 pmin 开始的子字符串 */
		fc_ch.words = &(fc_prs->words[fc_pmin]);
		/* 首先考虑长度为一的子字符串，然后考虑更长的子字符串 */
		fc_pmax = fc_pmin;
		do
		{
			/* 尝试将查询匹配到 pmin .. pmax 子字符串 */
			fc_ch.len = fc_pmax - fc_pmin + 1;
			if (TS_execute(GETQUERY(fc_query), &fc_ch,
						   TS_EXEC_EMPTY, fc_checkcondition_HL))
			{
				*fc_p = fc_pmin;
				*fc_q = fc_pmax;
				return true;
			}
			/* 不行，则将 pmax 前进到下一个可行的端点 */
			fc_nextpmax = fc_hlFirstIndex(fc_prs, fc_pmax + 1);

			/*
			 * 如果这是我们第一次在 pmin 之后推进，那么结果也是
			 * pmin 的下一个可行值；记住它以节省冗余搜索。
			 */
			if (fc_pmax == fc_pmin)
				fc_nextpmin = fc_nextpmax;
			fc_pmax = fc_nextpmax;
		}
		while (fc_pmax >= 0 && fc_pmax - fc_pmin < fc_max_cover);
		/* 运气不好，因此尝试下一个可行的起点 */
		fc_pmin = fc_nextpmin;
	}
	return false;
}

/*
 * 对由标题选择器选择的单词应用适当的高亮标记
 *
 * 从 startpos 到 endpos 的单词（包括）根据 highlightall 进行标记
 */
static void fc_mark_fragment(HeadlineParsedText *fc_prs, bool fc_highlightall,
			  int fc_startpos, int fc_endpos)
{
	int			fc_i;

	for (fc_i = fc_startpos; fc_i <= fc_endpos; fc_i++)
	{
		if (fc_prs->words[fc_i].item)
			fc_prs->words[fc_i].selected = 1;
		if (!fc_highlightall)
		{
			if (HLIDREPLACE(fc_prs->words[fc_i].type))
				fc_prs->words[fc_i].replace = 1;
			else if (HLIDSKIP(fc_prs->words[fc_i].type))
				fc_prs->words[fc_i].skip = 1;
		}
		else
		{
			if (XMLHLIDSKIP(fc_prs->words[fc_i].type))
				fc_prs->words[fc_i].skip = 1;
		}

		fc_prs->words[fc_i].in = (fc_prs->words[fc_i].repeated) ? 0 : 1;
	}
}

/*
 * 将覆盖子字符串拆分为不超过 max_words 的片段。
 *
 * 进入时，*startpos 和 *endpos 是覆盖子字符串的（剩余）边界。它们被更新以保存下一个片段的边界。
 *
 * *curlen 和 *poslen 设置为片段的长度，分别以单词和有趣单词计。
 */
static void fc_get_next_fragment(HeadlineParsedText *fc_prs, int *fc_startpos, int *fc_endpos,
				  int *fc_curlen, int *fc_poslen, int fc_max_words)
{
	int			fc_i;

	/*
	 * 目标：选择一个在 startpos 和 endpos 之间的单词片段，使其最多包含 max_words，并且两端都有查询单词。如果 startpos 和 endpos 是覆盖的端点，并且覆盖的单词少于 max_words，那么此函数应返回覆盖。
	 */
	/* 首先将 startpos 移动到一项 */
	for (fc_i = *fc_startpos; fc_i <= *fc_endpos; fc_i++)
	{
		*fc_startpos = fc_i;
		if (INTERESTINGWORD(fc_i))
			break;
	}
	/* 将 endpos 剪切为仅具有 max_words */
	*fc_curlen = 0;
	*fc_poslen = 0;
	for (fc_i = *fc_startpos; fc_i <= *fc_endpos && *fc_curlen < fc_max_words; fc_i++)
	{
		if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
			*fc_curlen += 1;
		if (INTERESTINGWORD(fc_i))
			*fc_poslen += 1;
	}
	/* 如果覆盖被剪切，则将 endpos 移回查询项 */
	if (*fc_endpos > fc_i)
	{
		*fc_endpos = fc_i;
		for (fc_i = *fc_endpos; fc_i >= *fc_startpos; fc_i--)
		{
			*fc_endpos = fc_i;
			if (INTERESTINGWORD(fc_i))
				break;
			if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
				*fc_curlen -= 1;
		}
	}
}

/*
 * 当 MaxFragments > 0 时使用的标题选择器
 *
 * 注意：在此模式下，highlightall 被忽略以进行短语选择；
 * 它只控制展示细节。
 */
static void fc_mark_hl_fragments(HeadlineParsedText *fc_prs, TSQuery fc_query, bool fc_highlightall,
				  int fc_shortword, int fc_min_words,
				  int fc_max_words, int fc_max_fragments, int fc_max_cover)
{
	int32		fc_poslen,
				fc_curlen,
				fc_i,
				fc_f,
				fc_num_f = 0;
	int32		fc_stretch,
				fc_maxstretch,
				fc_posmarker;

	int32		fc_startpos = 0,
				fc_endpos = 0,
				fc_p = 0,
				fc_q = 0;

	int32		fc_numcovers = 0,
				fc_maxcovers = 32;

	int32		fc_minI,
				fc_minwords,
				fc_maxitems;
	CoverPos   *fc_covers;

	fc_covers = palloc(fc_maxcovers * sizeof(CoverPos));

	/* 获取所有覆盖 */
	while (fc_hlCover(fc_prs, fc_query, fc_max_cover, &fc_p, &fc_q))
	{
		fc_startpos = fc_p;
		fc_endpos = fc_q;

		/*
		 * 将封面分成更小的片段，使每个片段最多包含
		 * max_words。还确保每个片段的两端都是
		 * 查询词。这将允许我们在任一方向上延展片段
		 */

		while (fc_startpos <= fc_endpos)
		{
			fc_get_next_fragment(fc_prs, &fc_startpos, &fc_endpos, &fc_curlen, &fc_poslen, fc_max_words);
			if (fc_numcovers >= fc_maxcovers)
			{
				fc_maxcovers *= 2;
				fc_covers = repalloc(fc_covers, sizeof(CoverPos) * fc_maxcovers);
			}
			fc_covers[fc_numcovers].startpos = fc_startpos;
			fc_covers[fc_numcovers].endpos = fc_endpos;
			fc_covers[fc_numcovers].curlen = fc_curlen;
			fc_covers[fc_numcovers].poslen = fc_poslen;
			fc_covers[fc_numcovers].chosen = false;
			fc_covers[fc_numcovers].excluded = false;
			fc_numcovers++;
			fc_startpos = fc_endpos + 1;
			fc_endpos = fc_q;
		}

		/* 移动 p 以生成下一个封面 */
		fc_p++;
	}

	/* 选择最佳封面 */
	for (fc_f = 0; fc_f < fc_max_fragments; fc_f++)
	{
		fc_maxitems = 0;
		fc_minwords = PG_INT32_MAX;
		fc_minI = -1;

		/*
		 * 选择包含最多项目的封面。如果有平局，选择
		 * 词数较少的一个。
		 */
		for (fc_i = 0; fc_i < fc_numcovers; fc_i++)
		{
			if (!fc_covers[fc_i].chosen && !fc_covers[fc_i].excluded &&
				(fc_maxitems < fc_covers[fc_i].poslen ||
				 (fc_maxitems == fc_covers[fc_i].poslen &&
				  fc_minwords > fc_covers[fc_i].curlen)))
			{
				fc_maxitems = fc_covers[fc_i].poslen;
				fc_minwords = fc_covers[fc_i].curlen;
				fc_minI = fc_i;
			}
		}
		/* 如果找到封面则标记它 */
		if (fc_minI >= 0)
		{
			fc_covers[fc_minI].chosen = true;
			/* 调整封面的大小 */
			fc_startpos = fc_covers[fc_minI].startpos;
			fc_endpos = fc_covers[fc_minI].endpos;
			fc_curlen = fc_covers[fc_minI].curlen;
			/* 如果封面大小低于 max_words，则延展封面 */
			if (fc_curlen < fc_max_words)
			{
				/* 在封面的两侧进行分割延展 */
				fc_maxstretch = (fc_max_words - fc_curlen) / 2;

				/*
				 * 首先延展起始位置，停止延展的条件是 1. 碰到
				 * 文档的开头 2. 超过最大延展 3. 碰到一个
				 * 已经标记过的片段
				 */
				fc_stretch = 0;
				fc_posmarker = fc_startpos;
				for (fc_i = fc_startpos - 1; fc_i >= 0 && fc_stretch < fc_maxstretch && !fc_prs->words[fc_i].in; fc_i--)
				{
					if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
					{
						fc_curlen++;
						fc_stretch++;
					}
					fc_posmarker = fc_i;
				}
				/* 将起始位置收回，直到找到一个好的终点 */
				for (fc_i = fc_posmarker; fc_i < fc_startpos && BADENDPOINT(fc_i); fc_i++)
				{
					if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
						fc_curlen--;
				}
				fc_startpos = fc_i;
				/* 现在尽可能延展结束位置 */
				fc_posmarker = fc_endpos;
				for (fc_i = fc_endpos + 1; fc_i < fc_prs->curwords && fc_curlen < fc_max_words && !fc_prs->words[fc_i].in; fc_i++)
				{
					if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
						fc_curlen++;
					fc_posmarker = fc_i;
				}
				/* 将结束位置收回，直到找到一个好的终点 */
				for (fc_i = fc_posmarker; fc_i > fc_endpos && BADENDPOINT(fc_i); fc_i--)
				{
					if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
						fc_curlen--;
				}
				fc_endpos = fc_i;
			}
			fc_covers[fc_minI].startpos = fc_startpos;
			fc_covers[fc_minI].endpos = fc_endpos;
			fc_covers[fc_minI].curlen = fc_curlen;
			/* 标记选择的片段（封面） */
			fc_mark_fragment(fc_prs, fc_highlightall, fc_startpos, fc_endpos);
			fc_num_f++;
			/* 从今后的考虑中排除与此重叠的封面 */
			for (fc_i = 0; fc_i < fc_numcovers; fc_i++)
			{
				if (fc_i != fc_minI &&
					((fc_covers[fc_i].startpos >= fc_startpos &&
					  fc_covers[fc_i].startpos <= fc_endpos) ||
					 (fc_covers[fc_i].endpos >= fc_startpos &&
					  fc_covers[fc_i].endpos <= fc_endpos) ||
					 (fc_covers[fc_i].startpos < fc_startpos &&
					  fc_covers[fc_i].endpos > fc_endpos)))
					fc_covers[fc_i].excluded = true;
			}
		}
		else
			break;				/* 没有可以选择的封面 */
	}

	/* 如果我们没有标记任何内容，则显示前 min_words 个词 */
	if (fc_num_f <= 0)
	{
		fc_startpos = fc_curlen = 0;
		fc_endpos = -1;
		for (fc_i = 0; fc_i < fc_prs->curwords && fc_curlen < fc_min_words; fc_i++)
		{
			if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
				fc_curlen++;
			fc_endpos = fc_i;
		}
		fc_mark_fragment(fc_prs, fc_highlightall, fc_startpos, fc_endpos);
	}

	pfree(fc_covers);
}

/*
 * 当 MaxFragments == 0 时使用的标题选择器
 */
static void fc_mark_hl_words(HeadlineParsedText *fc_prs, TSQuery fc_query, bool fc_highlightall,
			  int fc_shortword, int fc_min_words, int fc_max_words, int fc_max_cover)
{
	int			fc_p = 0,
				fc_q = 0;
	int			fc_bestb = -1,
				fc_beste = -1;
	int			fc_bestlen = -1;
	bool		fc_bestcover = false;
	int			fc_pose,
				fc_posb,
				fc_poslen,
				fc_curlen;
	bool		fc_poscover;
	int			fc_i;

	if (!fc_highlightall)
	{
		/* 检查所有封面，选择最佳的标题 */
		while (fc_hlCover(fc_prs, fc_query, fc_max_cover, &fc_p, &fc_q))
		{
			/*
			 * 计算封面内的词数（curlen）和有趣的词数（poslen），
			 * 但一旦达到 max_words 就停止。这一步不考虑
			 * 是否是一个好的停止点。posb 和 pose 设置为
			 * 可能标题的开始和结束索引。
			 */
			fc_curlen = 0;
			fc_poslen = 0;
			fc_posb = fc_pose = fc_p;
			for (fc_i = fc_p; fc_i <= fc_q && fc_curlen < fc_max_words; fc_i++)
			{
				if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
					fc_curlen++;
				if (INTERESTINGWORD(fc_i))
					fc_poslen++;
				fc_pose = fc_i;
			}

			if (fc_curlen < fc_max_words)
			{
				/*
				 * 我们有足够的空间来延长标题，所以向前搜索，
				 * 直到它填满或找到一个好的停止点。我们将
				 * 重新考虑在“q”处的单词，然后继续向前。
				 */
				for (fc_i = fc_i - 1; fc_i < fc_prs->curwords && fc_curlen < fc_max_words; fc_i++)
				{
					if (fc_i > fc_q)
					{
						if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
							fc_curlen++;
						if (INTERESTINGWORD(fc_i))
							fc_poslen++;
					}
					fc_pose = fc_i;
					if (BADENDPOINT(fc_i))
						continue;
					if (fc_curlen >= fc_min_words)
						break;
				}
				if (fc_curlen < fc_min_words)
				{
					/*
					 * 达到文本末尾且我们的标题仍然短于
					 * min_words，因此尝试向左扩展。
					 */
					for (fc_i = fc_p - 1; fc_i >= 0; fc_i--)
					{
						if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
							fc_curlen++;
						if (INTERESTINGWORD(fc_i))
							fc_poslen++;
						if (fc_curlen >= fc_max_words)
							break;
						if (BADENDPOINT(fc_i))
							continue;
						if (fc_curlen >= fc_min_words)
							break;
					}
					fc_posb = (fc_i >= 0) ? fc_i : 0;
				}
			}
			else
			{
				/*
				 * 无法使标题更长，因此考虑在必要时缩短它
				 * 以避免不好的终点。
				 */
				if (fc_i > fc_q)
					fc_i = fc_q;
				for (; fc_curlen > fc_min_words; fc_i--)
				{
					if (!BADENDPOINT(fc_i))
						break;
					if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
						fc_curlen--;
					if (INTERESTINGWORD(fc_i))
						fc_poslen--;
					fc_pose = fc_i - 1;
				}
			}

			/*
			 * 检查提议的标题是否包含原始封面；
			 * 如果由于 max_words 被修剪，它可能不包含。
			 */
			fc_poscover = (fc_posb <= fc_p && fc_pose >= fc_q);

			/*
			 * 如果这个标题比上一个更好，则采用它，
			 * 优先考虑包含封面的标题，其次是有更多
			 * 有趣词的标题，最后是有好的停止点的标题。
			 * （由于 bestlen 最初为 -1，我们肯定会
			 * 采用第一个标题。）
			 */
			if (fc_poscover > fc_bestcover ||
				(fc_poscover == fc_bestcover && fc_poslen > fc_bestlen) ||
				(fc_poscover == fc_bestcover && fc_poslen == fc_bestlen &&
				 !BADENDPOINT(fc_pose) && BADENDPOINT(fc_beste)))
			{
				fc_bestb = fc_posb;
				fc_beste = fc_pose;
				fc_bestlen = fc_poslen;
				fc_bestcover = fc_poscover;
			}

			/* 移动 p 以生成下一个封面 */
			fc_p++;
		}

		/*
		 * 如果我们没有找到可以接受的选择，从
		 * 开始选择 min_words 个词。
		 */
		if (fc_bestlen < 0)
		{
			fc_curlen = 0;
			fc_pose = -1;
			for (fc_i = 0; fc_i < fc_prs->curwords && fc_curlen < fc_min_words; fc_i++)
			{
				if (!NONWORDTOKEN(fc_prs->words[fc_i].type))
					fc_curlen++;
				fc_pose = fc_i;
			}
			fc_bestb = 0;
			fc_beste = fc_pose;
		}
	}
	else
	{
		/* highlightall 模式：标题是整个文档 */
		fc_bestb = 0;
		fc_beste = fc_prs->curwords - 1;
	}

	fc_mark_fragment(fc_prs, fc_highlightall, fc_bestb, fc_beste);
}

/*
 * 默认解析器的 prsheadline 函数
 */
Datum prsd_headline(PG_FUNCTION_ARGS)
{
	HeadlineParsedText *fc_prs = (HeadlineParsedText *) PG_GETARG_POINTER(0);
	List	   *fc_prsoptions = (List *) PG_GETARG_POINTER(1);
	TSQuery		fc_query = PG_GETARG_TSQUERY(2);

	/* 默认选项值： */
	int			fc_min_words = 15;
	int			fc_max_words = 35;
	int			fc_shortword = 3;
	int			fc_max_fragments = 0;
	bool		fc_highlightall = false;
	int			fc_max_cover;
	ListCell   *fc_l;

	/* 提取配置选项值 */
	fc_prs->startsel = NULL;
	fc_prs->stopsel = NULL;
	fc_prs->fragdelim = NULL;
	foreach(fc_l, fc_prsoptions)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_l);
		char	   *fc_val = defGetString(fc_defel);

		if (pg_strcasecmp(fc_defel->defname, "MaxWords") == 0)
			fc_max_words = pg_strtoint32(fc_val);
		else if (pg_strcasecmp(fc_defel->defname, "MinWords") == 0)
			fc_min_words = pg_strtoint32(fc_val);
		else if (pg_strcasecmp(fc_defel->defname, "ShortWord") == 0)
			fc_shortword = pg_strtoint32(fc_val);
		else if (pg_strcasecmp(fc_defel->defname, "MaxFragments") == 0)
			fc_max_fragments = pg_strtoint32(fc_val);
		else if (pg_strcasecmp(fc_defel->defname, "StartSel") == 0)
			fc_prs->startsel = pstrdup(fc_val);
		else if (pg_strcasecmp(fc_defel->defname, "StopSel") == 0)
			fc_prs->stopsel = pstrdup(fc_val);
		else if (pg_strcasecmp(fc_defel->defname, "FragmentDelimiter") == 0)
			fc_prs->fragdelim = pstrdup(fc_val);
		else if (pg_strcasecmp(fc_defel->defname, "HighlightAll") == 0)
			fc_highlightall = (pg_strcasecmp(fc_val, "1") == 0 ||
							pg_strcasecmp(fc_val, "on") == 0 ||
							pg_strcasecmp(fc_val, "true") == 0 ||
							pg_strcasecmp(fc_val, "t") == 0 ||
							pg_strcasecmp(fc_val, "y") == 0 ||
							pg_strcasecmp(fc_val, "yes") == 0);
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unrecognized headline parameter: \"%s\"",
							fc_defel->defname)));
	}

	/*
	 * 我们可能最终会让 max_cover 成为用户可设置的参数，但目前
	 * 只是根据 max_words 和 max_fragments 计算一个合理的值。
	 */
	fc_max_cover = Max(fc_max_words * 10, 100);
	if (fc_max_fragments > 0)
		fc_max_cover *= fc_max_fragments;

	/* 在 HighlightAll 模式下，这些参数被忽略 */
	if (!fc_highlightall)
	{
		if (fc_min_words >= fc_max_words)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("MinWords should be less than MaxWords")));
		if (fc_min_words <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("MinWords should be positive")));
		if (fc_shortword < 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("ShortWord should be >= 0")));
		if (fc_max_fragments < 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("MaxFragments should be >= 0")));
	}

	/* 应用适当的标题选择器 */
	if (fc_max_fragments == 0)
		fc_mark_hl_words(fc_prs, fc_query, fc_highlightall, fc_shortword,
					  fc_min_words, fc_max_words, fc_max_cover);
	else
		fc_mark_hl_fragments(fc_prs, fc_query, fc_highlightall, fc_shortword,
						  fc_min_words, fc_max_words, fc_max_fragments, fc_max_cover);

	/* 填充字符串选项的默认值 */
	if (!fc_prs->startsel)
		fc_prs->startsel = pstrdup("<b>");
	if (!fc_prs->stopsel)
		fc_prs->stopsel = pstrdup("</b>");
	if (!fc_prs->fragdelim)
		fc_prs->fragdelim = pstrdup(" ... ");

	/* 调用者也需要这些长度 */
	fc_prs->startsellen = strlen(fc_prs->startsel);
	fc_prs->stopsellen = strlen(fc_prs->stopsel);
	fc_prs->fragdelimlen = strlen(fc_prs->fragdelim);

	PG_RETURN_POINTER(fc_prs);
}
